﻿using Loong.Threading.Locks;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace Loong.Runtime.Caching
{
    public abstract class CacheBase : ICache
    {
        private readonly ILockFactory _lockFactory;

        protected ILogger Logger { get; private set; }

        protected CacheBase(ILockFactory lockFactory, ILogger logger)
        {
            _lockFactory = lockFactory;

            Logger = logger;
        }

        public virtual object GetOrCreate(string key, Func<CacheOptions, object> factory)
        {
            object item = null;

            try
            {
                item = Get(key);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex.ToString(), ex);
            }

            if (item == null)
            {
                using (_lockFactory.LockAsync(BuildLockResource(key), _lockFactory.GetOrCreateOwner()).Result)
                {
                    try
                    {
                        item = Get(key);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex.ToString(), ex);
                    }

                    if (item == null)
                    {
                        var options = new CacheOptions();

                        item = factory(options);

                        if (item == null)
                        {
                            return null;
                        }

                        try
                        {
                            Set(key, item, options.ExpireTime);
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError(ex.ToString(), ex);
                        }
                    }
                }
            }

            return item;
        }

        public virtual TValue GetOrCreate<TValue, TKey>(TKey key, Func<CacheOptions, TValue> factory)
        {
            var options = new CacheOptions();

            var value = (object)factory(options);

            return (TValue)GetOrCreate(key.ToString(), o =>
            {
                o.ExpireTime = options.ExpireTime;

                return value;
            });
        }

        public virtual async Task<object> GetOrCreateAsync(string key, Func<CacheOptions, Task<object>> factory)
        {
            object item = null;

            try
            {
                item = await GetAsync(key);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex.ToString(), ex);
            }

            if (item == null)
            {
                using (await _lockFactory.LockAsync(BuildLockResource(key), _lockFactory.GetOrCreateOwner()))
                {
                    try
                    {
                        item = await GetAsync(key);
                    }
                    catch (Exception ex)
                    {
                        Logger.LogError(ex.ToString(), ex);
                    }

                    if (item == null)
                    {
                        var options = new CacheOptions();

                        item = await factory(options);

                        if (item == null)
                        {
                            return null;
                        }

                        try
                        {
                            await SetAsync(key, item, options.ExpireTime);
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError(ex.ToString(), ex);
                        }
                    }
                }
            }

            return item;
        }

        public virtual async Task<TValue> GetOrCreateAsync<TValue, TKey>(TKey key, Func<CacheOptions, Task<TValue>> factory)
        {
            var options = new CacheOptions();

            var value = (object)await factory(options);

            return (TValue)await GetOrCreateAsync(key.ToString(), o =>
            {
                o.ExpireTime = options.ExpireTime;

                return Task.FromResult(value);
            });
        }

        protected string BuildLockResource(string key)
        {
            return $"Cache:{key}";
        }

        public abstract object Get(string key);

        public virtual TValue Get<TValue, TKey>(TKey key)
        {
            return (TValue)Get(key.ToString());
        }

        public virtual Task<object> GetAsync(string key)
        {
            return Task.Run(() => Get(key));
        }

        public virtual async Task<TValue> GetAsync<TValue, TKey>(TKey key)
        {
            var value = await GetAsync(key.ToString());

            return (TValue)value;
        }

        public abstract void Set(string key, object value, TimeSpan expireTime);

        public virtual void Set<TValue, TKey>(TKey key, TValue value, TimeSpan expireTime)
        {
            Set(key.ToString(), (object)value, expireTime);
        }

        public virtual Task SetAsync(string key, object value, TimeSpan expireTime)
        {
            return Task.Run(() => Set(key, value, expireTime));
        }

        public virtual Task SetAsync<TValue, TKey>(TKey key, TValue value, TimeSpan expireTime)
        {
            return SetAsync(key.ToString(), (object)value, expireTime);
        }

        public abstract void Remove(string key);

        public virtual void Remove<TKey>(TKey key)
        {
            Remove(key.ToString());
        }

        public virtual Task RemoveAsync(string key)
        {
            return Task.Run(() => Remove(key));
        }

        public virtual Task RemoveAsync<TKey>(TKey key)
        {
            return RemoveAsync(key.ToString());
        }

        public abstract void Clear();

        public virtual Task ClearAsync()
        {
            return Task.Run(() => Clear());
        }
    }
}
